package org.fhawk.tinyspring.core.support;

import org.fhawk.tinyspring.util.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by tuo on 8/11/2016.
 */
public abstract class ClassTemplate {
    private static Logger logger = LoggerFactory.getLogger(ClassTemplate.class);
    private static final String CLASS_SUFFIX = ".class";
    String basePackage;

    public ClassTemplate(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * 获取噢诶之稳健指定包下的所有class文件
     *
     * @return
     */
    public final List<Class<?>> getClassList() {
        List<Class<?>> classes = new ArrayList<>();
        try {
            //获取指定包下的所有资源
            Enumeration<URL> urls = ClassUtil.getClassLoader().getResources(basePackage.replace(".", "/"));
            //转换为list 方便使用stream
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (null != url) {
                    switch (url.getProtocol()) {
                        case "file":
                            doFile(classes, url);
                            break;
                        case "jar":
                            doJar(classes, url);
                            break;
                        default:
                            break;
                    }
                }
            }
        } catch (IOException e) {
            logger.error("get base package classes error", e);
        }
        return classes;
    }


    private void doFile(List<Class<?>> classes, URL url) {
        String packagePath = url.getPath().replaceAll("%20", " ");
        if (packagePath.contains(":") && packagePath.startsWith("/")) {
            packagePath = packagePath.replaceFirst("/", "");
        }
        addClass(classes, packagePath);
    }

    private void doJar(List<Class<?>> classes, URL url) {
        try {
            JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
            JarFile jarFile = jarURLConnection.getJarFile();
            Enumeration<JarEntry> jarEntries = jarFile.entries();
            while (jarEntries.hasMoreElements()) {
                JarEntry jarEntry = jarEntries.nextElement();
                String jarEntryName = jarEntry.getName();
                // 判断该 entry 是否为 class
                if (jarEntryName.endsWith(CLASS_SUFFIX)) {
                    // 获取类名
                    String className = jarEntryName.substring(0, jarEntryName.lastIndexOf("")).replace("/", "");
                    // 执行添加类操作
                    doAddClass(classes, className);
                }
            }
        } catch (IOException e) {
            logger.error("get base package classes error", e);
        }
    }


    private void addClass(List<Class<?>> classList, String packagePath) {
        Path path = Paths.get(packagePath);
        try {
            Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    if (file.getFileName().toString().endsWith(CLASS_SUFFIX)) {
                        String path = file.getParent().toString();
                        String other = basePackage.replace(".", File.separator);
                        int index = path.indexOf(other);
                        String className = path.substring(index, path.length()) + File.separator + file.getFileName().toString().replace(CLASS_SUFFIX, "");
                        doAddClass(classList, className.replace(File.separator, "."));
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            logger.error("扫描包{}下的类文件失败。", e);
        }
    }

    private void doAddClass(List<Class<?>> classList, String className) {
        // 加载类
        Class<?> cls = ClassUtil.loadClass(className, false);
        // 判断是否可以添加类
        if (checkAddClass(cls)) {
            // 添加类
            classList.add(cls);
        }
    }

    /**
     * 判断是否应该添加类
     *
     * @param cls
     * @return
     */
    public abstract boolean checkAddClass(Class<?> cls);
}
